package cre.tomcat4.ch03.connector;

import cre.tomcat4.ch03.connector.http.*;
import cre.tomcat4.ch03.processor.ServletProcessor;
import cre.tomcat4.ch03.processor.StaticResourceProcessor;
import org.apache.catalina.util.RequestUtil;
import org.apache.catalina.util.StringManager;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

import static cre.tomcat4.ch03.Constants.HTTP_PACKAGE;

/**
 * @Author penglai
 * @Date 2017/1/4.
 */
public class HttpProcessor {

    private HttpRequest request;
    private HttpResponse response;

    private HttpRequestLine httpRequestLine = new HttpRequestLine();
    private StringManager sm = StringManager.getManager(HTTP_PACKAGE);

    public void process(Socket socket) {
        OutputStream outputStream = null;
        SocketInputStream inputStream;

        try {
            inputStream = new SocketInputStream(socket.getInputStream(), 2048);
            outputStream = socket.getOutputStream();

            request = new HttpRequest(inputStream);
            response = new HttpResponse(outputStream);
            response.setRequest(request);

            response.setHeader("Server", "CreGu Tomcat Server");

            parseRequest(inputStream, outputStream);
            parseHeaders(inputStream);

            //check request type
            if (request.getRequestURI().startsWith("/servlet/")) {
                ServletProcessor processor = new ServletProcessor();
                processor.process(request, response);
            } else {
                StaticResourceProcessor processor = new StaticResourceProcessor();
                processor.process(request, response);
            }
//            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void parseRequest(SocketInputStream inputStream, OutputStream outputStream)
            throws IOException, ServletException {
        inputStream.readRequestLine(httpRequestLine);

        String method = new String(httpRequestLine.method, 0, httpRequestLine.methodEnd);
        String uri = null;
        String protocol = new String(httpRequestLine.protocol, 0, httpRequestLine.protocolEnd);

        if (method.length() < 1) {
            throw new ServletException("Miss HTTP request method");
        }
        if (httpRequestLine.uriEnd < 1) {
            throw new ServletException("Miss HTTP request uri");
        }

        int query = httpRequestLine.indexOf("?");
        if (query >= 0) {
            request.setQueryString(new String(httpRequestLine.uri, query + 1, httpRequestLine.uriEnd - query - 1));
            uri = new String(httpRequestLine.uri, 0, query);
        } else {
            request.setQueryString(null);
            uri = new String(httpRequestLine.uri, 0, httpRequestLine.uriEnd);
        }

        if (!uri.startsWith("/")) {
            int pos = uri.indexOf("://");
            if (pos != -1) {
                pos = uri.indexOf("/", pos + 3);
                if (pos == -1) {
                    uri = "";
                } else {
                    uri = uri.substring(pos);
                }
            }
        }

        String sessionIdMatch = ";jsessionid=";
        int sin = uri.indexOf(sessionIdMatch);
        if (sin >= 0) {
            String rest = uri.substring(sin + sessionIdMatch.length());
            int sin2 = rest.indexOf(";");
            if (sin2 >= 0) {
                request.setRequestedSessionId(rest.substring(0, sin2));
                rest = rest.substring(sin2);
            } else {
                request.setRequestedSessionId(rest);
                rest = "";
            }
            request.setRequestedSessionURL(true);
            uri = uri.substring(0, sin) + rest;
        } else {
            request.setRequestedSessionId(null);
            request.setRequestedSessionURL(false);
        }

        String normalizedUri = normalize(uri);
        if (normalizedUri != null) {
            request.setRequestURI(normalizedUri);
        } else {
            request.setRequestURI(uri);
        }
        if (normalizedUri == null) {
            throw new ServletException("Invalid URI: " + uri);
        }
        request.setMethod(method);
        request.setProtocol(protocol);
    }

    protected String normalize(String path) {
        if (path == null)
            return null;
        // Create a place for the normalized path
        String normalized = path;

        // Normalize "/%7E" and "/%7e" at the beginning to "/~"
        if (normalized.startsWith("/%7E") || normalized.startsWith("/%7e"))
            normalized = "/~" + normalized.substring(4);

        // Prevent encoding '%', '/', '.' and '\', which are special reserved
        // characters
        if ((normalized.indexOf("%25") >= 0)
                || (normalized.indexOf("%2F") >= 0)
                || (normalized.indexOf("%2E") >= 0)
                || (normalized.indexOf("%5C") >= 0)
                || (normalized.indexOf("%2f") >= 0)
                || (normalized.indexOf("%2e") >= 0)
                || (normalized.indexOf("%5c") >= 0)) {
            return null;
        }

        if (normalized.equals("/."))
            return "/";

        // Normalize the slashes and add leading slash if necessary
        if (normalized.indexOf('\\') >= 0)
            normalized = normalized.replace('\\', '/');
        if (!normalized.startsWith("/"))
            normalized = "/" + normalized;

        // Resolve occurrences of "//" in the normalized path
        while (true) {
            int index = normalized.indexOf("//");
            if (index < 0)
                break;
            normalized = normalized.substring(0, index) +
                    normalized.substring(index + 1);
        }

        // Resolve occurrences of "/./" in the normalized path
        while (true) {
            int index = normalized.indexOf("/./");
            if (index < 0)
                break;
            normalized = normalized.substring(0, index) +
                    normalized.substring(index + 2);
        }

        // Resolve occurrences of "/../" in the normalized path
        while (true) {
            int index = normalized.indexOf("/../");
            if (index < 0)
                break;
            if (index == 0)
                return (null);  // Trying to go outside our context
            int index2 = normalized.lastIndexOf('/', index - 1);
            normalized = normalized.substring(0, index2) +
                    normalized.substring(index + 3);
        }

        // Declare occurrences of "/..." (three or more dots) to be invalid
        // (on some Windows platforms this walks the directory tree!!!)
        if (normalized.indexOf("/...") >= 0)
            return (null);

        // Return the normalized path that we have completed
        return (normalized);

    }

    private void parseHeaders(SocketInputStream inputStream) throws IOException, ServletException {
        while (true) {
            HttpHeader header = new HttpHeader();
            inputStream.readHeader(header);

            if (header.nameEnd == 0) {
                if (header.valueEnd == 0) {
                    return;
                } else {
                    throw new ServletException(sm.getString("httpProcessor.parseHeader.colon"));
                }
            }

            String name = new String(header.name, 0, header.nameEnd);
            String value = new String(header.value, 0, header.valueEnd);
            request.addHeader(name, value);

            if (name.equals("cookie")) {
                Cookie cookies[] = RequestUtil.parseCookieHeader(value);
                for (int i = 0; i < cookies.length; i++) {
                    if (cookies[i].getName().equals("jsessionid")) {
                        // Override anything requested in the URL
                        if (!request.isRequestedSessionIdFromCookie()) {
                            // Accept only the first session id cookie
                            request.setRequestedSessionId(cookies[i].getValue());
                            request.setRequestedSessionCookie(true);
                            request.setRequestedSessionURL(false);
                        }
                    }
                    request.addCookie(cookies[i]);
                }
            } else if (name.equals("content-length")) {
                int n = -1;
                try {
                    n = Integer.parseInt(value);
                } catch (Exception e) {
                    throw new ServletException(sm.getString("httpProcessor.parseHeaders.contentLength"));
                }
                request.setContentLength(n);
            } else if (name.equals("content-type")) {
                request.setContentType(value);
            }

        }

    }

}
